//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

#include "pch.h"
#include "LoggingChannelScenario.h"

using namespace winrt;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Diagnostics;
using namespace winrt::SDKTemplate;

/// <summary>
/// Construct a LoggingChannel with the Windows 8.1 constructor, then
/// use the LoggingChannel for the scenario.
/// See this sample's README for further discussion.
/// </summary>
void
LoggingChannelScenario::LogWithWin81Constructor()
{
    // The 1-parameter constructor creates a channel with Windows 8.1 semantics.
    LoggingChannel channel(L"SampleProvider");
    DemonstrateLogging(channel);
}

/// <summary>
/// Construct a LoggingChannel with the Windows 10 constructor, then
/// use the LoggingChannel for the scenario. Also show the use cases
/// for the two Windows 10 constructors.
// See this sample's README for further discussion.
/// </summary>
void
LoggingChannelScenario::LogWithWin10Constructor()
{
    {
        // The 2-parameter constructor creates a channel with Windows 10 semantics.
        LoggingChannel channel(
            L"SampleProvider",
            nullptr); // null means use default options.

        // The Id is generated by hashing the string "SampleProvider".
        // channel.Id == eff1e128-4903-5093-096a-bdc29b38456f
        DemonstrateLogging(channel);
    }

    /*
    Demonstrate other (less-common) constructor scenarios:
    */

    {
        // This creates a channel with Windows 10 semantics and declared
        // membership in a provider group.
        GUID g = { 0x2e0582f3,0xd1b6,0x516a,0x9d,0xe3,0x9f,0xd7,0x9e,0xf9,0x52,0xf8 };
        LoggingChannel channel(
            L"SampleProvider",
            Windows::Foundation::Diagnostics::LoggingChannelOptions(g)); // Join a provider group
        // The Id is generated by hashing the string "SampleProvider".
        // channel.Id == eff1e128-4903-5093-096a-bdc29b38456f
    }

    {
        // This creates a channel with Windows 10 semantics and a specific
        // provider Id.
        GUID g = { 0x2e0582f3,0xd1b6,0x516a,0x9d,0xe3,0x9f,0xd7,0x9e,0xf9,0x52,0xf8 };
        LoggingChannel channel(
            L"SampleProvider",
            nullptr,
            g);
        // channel.Id == 2e0582f3-d1b6-516a-9de3-9fd79ef952f8
    }
}

/// <summary>
/// This method demonstrates the LoggingChannel and LoggingActivity APIs.
/// </summary>
/// <param name="channel">
/// The channel to use for the demonstration. This channel may have been
/// constructed using a Windows 8.1 constructor or a Windows 10 constructor.
/// The same APIs are supported in both cases, but the ETL events will be
/// formatted a bit differently depending on how the channel was constructed.
/// </param>
void
LoggingChannelScenario::DemonstrateLogging(
    LoggingChannel const& channel)
{
    // Whenever any ETW session changes the way it is listening to this
    // channel, the LoggingEnable event is fired. For example, this might
    // be called when a session begins listening, changes the level at
    // which it is listening, or stops listening.
    event_token loggingEnabledToken = channel.LoggingEnabled({ this, &LoggingChannelScenario::OnLoggingEnabled });

    // Log simple string events
    channel.LogMessage(L"Simple message"); // default level is Verbose
    channel.LogMessage(L"Simple error", Windows::Foundation::Diagnostics::LoggingLevel::Error);

    // Log simple string + integer events.
    channel.LogValuePair(L"Simple message", 123); // default level is Verbose
    channel.LogValuePair(L"Simple error", 456, LoggingLevel::Error);

    // The channel.Name property returns the name that was used when the
    // channel was constructed. When running in Windows 10 mode, the name
    // is already set as the provider name, so no LoggingChannelName is
    // automatically added to the event.
    channel.LogMessage(channel.Name());

    // The channel.Id property is new to Windows 10.
    channel.LogMessage(to_hstring(channel.Id()));

    // If you want to avoid the overhead of collecting data when nobody is
    // listening to your channel, check the Enabled property before logging.
    if (channel.Enabled())
    {
        channel.LogMessage(CollectExpensiveData());
    }

    // The IsEnabled() method is exactly the same as the Enabled property,
    // except that it is a new Windows 10 API.
    if (channel.IsEnabled())
    {
        channel.LogMessage(CollectExpensiveData());
    }

    // If you want to only collect data if somebody is listening at a specific
    // level, you need to check both Enabled and Level. Note that the value of
    // the Level is unspecified when Enabled is false.
    if (channel.Enabled() && channel.Level() <= LoggingLevel::Warning)
    {
        channel.LogMessage(CollectExpensiveData(), LoggingLevel::Warning);
    }

    // The IsEnabled(LoggingLevel) method is a bit nicer than checking both
    // Enabled and Level, but it is only available on Windows 10 or later.
    if (channel.IsEnabled(LoggingLevel::Warning))
    {
        channel.LogMessage(CollectExpensiveData(), LoggingLevel::Warning);
    }

    // You can also use IsEnabled to check for keywords.
    if (channel.IsEnabled(LoggingLevel::Information, 0x10))
    {
        channel.LogMessage(CollectExpensiveData(), LoggingLevel::Information);
    }

    // Use LoggingFields with the LogEvent method to write complex events.
    Windows::Foundation::Diagnostics::LoggingFields fields;
    fields.AddDouble(L"pi", 3.14159);
    channel.LogEvent(
        L"ComplexEvent",
        fields,
        LoggingLevel::Verbose,
        LoggingOptions(0x10)); // Keywords = 0x10

    // You can add any number of name-value pairs to a fields object, though
    // you may encounter ETW limitations if you add too many. For example,
    // ETW is limited to a maximum event size of 64KB, and the current
    // TraceLogging decoder can handle no more than 128 fields.

    // Performance optimization: You can reuse a LoggingFields object to
    // avoid unnecessary allocations. Don't forget to call Clear()
    // between uses, and don't try to share a LoggingFields object between
    // threads.
    DateTime now = clock::now();
    fields.Clear();
    fields.AddInt64(L"Date", clock::to_file_time(now).value, LoggingFieldFormat::FileTime);
    channel.LogEvent(L"Now", fields);

    fields.Clear();

    // You can add a formatting hint to affect the way a value is decoded.
    // Not all combinations are useful, and the hint may be ignored.
    // For example, you can encode an MBCS string by writing a byte array
    // with a String hint.
    uint8_t abc123[] = { 65, 66, 67, 49, 50, 51 }; // "ABC123"
    fields.AddUInt8Array(
        L"AnsiString",
        abc123,
        LoggingFieldFormat::String);

    // You can add "tag" bits to a field. These are user-defined bits that
    // can be used to communicate with an event processing tool. For example,
    // you might define a tag bit to indicate that a field contains private
    // data that should not be displayed on-screen.
    fields.AddString(L"Password", L"12345", LoggingFieldFormat::Default, 0x10);

    // You can add a "structure" to an event. A structure is a name for a
    // group of fields. Structures can nest. Call BeginStruct to add a level
    // of nesting, and call EndStruct after the last field of the structure.
    fields.BeginStruct(L"Nested");
    fields.AddInt16(L"Nested-1", 1);
    fields.AddInt16(L"Nested-2", 2);
    fields.BeginStruct(L"Nested-Nested");
    fields.AddInt16(L"Nested-Nested-3", 3);
    fields.EndStruct();
    fields.AddInt16(L"Nested-4", 4);
    fields.EndStruct();

    // Advanced scenarios: you can use a LoggingOptions object to control
    // detailed event settings such as keywords, opcodes, and activity Ids.
    // These have their normal ETW semantics. You can also set event tags,
    // which are bit values that can be used to communicate with the event
    // processor.
    LoggingOptions options;
    options.Keywords(0x123);
    options.Tags(0x10);
    channel.LogEvent(
        L"VeryComplexEvent",
        fields,
        LoggingLevel::Information,
        options);

    // Windows 10 introduces the ILoggingTarget interface. LoggingChannel
    // implements this interface. This interface allows components to accept
    // a logger as an parameter.
    DoSomething(channel);

    /*
    If a LoggingActivity is created using a LoggingActivity constructor,
    it will use Windows 8.1 semantics:

    - If an activity is destroyed (garbage-collected) without being closed
    and the associated LoggingChannel is still open, the activity will
    write a default Stop event.
    - The default Stop event (written by the destructor or by the Close()
    method) is encoded as a "simple" event.

    The 8.1 semantics are deprecated because the automatic generation of
    a Stop event at garbage-collection can be misleading. The Stop event
    is intended to mark the a precise point at which an activity is
    completed, while the garbage-collection of an abandoned activity is
    inherently imprecise and unpredictable.

    If a LoggingActivity is created using a StartActivity method, it will
    use Windows 10 semantics:

    - If an activity is destroyed (garbage-collected) without being closed,
      there will be no Stop event for the activity.
    - The default Stop event (written by the Close() method) is encoded as
      a TraceLogging event with name "ActivityClosed".
    */

    {
        // This activity is created with Windows 8.1 semantics.
        LoggingActivity a1(L"Activity1", channel);

        // The activity Start event is written by the LoggingActivity constructor.
        // You would do your activity's work here.
        // The activity Stop event is written when the activity is closed (disposed).

        // The Windows 10 LoggingActivity adds new methods for writing events
        // that are marked as associated with the activity.
        a1.LogEvent(L"Activity event");

        // LoggingActivity also implements the ILoggingTarget interface, so you can
        // use either a channel or an activity as a logging target.
        DoSomething(a1);

        {
            // The Windows 10 LoggingActivity adds new methods for creating nested activities.
            // Note that nested activities are always created with Windows 10 semantics,
            // even when nested under an activity that is using Windows 8.1 semantics.
            LoggingActivity a2 = a1.StartActivity(L"Activity2");

            // Nested task occurs here.

            // The Windows 10 LoggingActivity allows you to customize the Stop event.
            a2.StopActivity(L"Activity 2 stop");
        }

        // Because a1 is using Windows 8.1 semantics, it will write a Stop event when
        // it goes out of scope.
    }

    {
        // The Windows 10 StartActivity method creates a new activity, optionally with
        // specified fields and characteristics.
        // This activity is created with Windows 10 semantics.
        LoggingActivity a3 = channel.StartActivity(L"Activity3");

        // Because a3 is using Windows 10 semantics, if we did not call StopActivity(),
        // there would be no Stop event (not even when the activity is garbage
        // collected). To get a Stop event, be sure to stop, close, or dispose the
        // activity.
        a3.StopActivity(L"ActivityClosed");
    }

    // Unregister from the event.
    channel.LoggingEnabled(loggingEnabledToken);
}

hstring
LoggingChannelScenario::CollectExpensiveData()
{
    return L"ExpensiveData";
}

void
LoggingChannelScenario::DoSomething(
    ILoggingTarget const& logger)
{
    logger.LogEvent(L"Did something");
}

void
LoggingChannelScenario::OnLoggingEnabled(
    ILoggingChannel const&,
    IInspectable const&)
{
    // Here, you could note a change in the level or keywords.
}
